/** 
 * File: MonthDayYear.java
 * Project: CMSC 345 Course Registration System, Fall 2010
 * Author: David Chong
 * Date: 12/5/10
 * E-mail: ro62899@umbc.edu
 * This class is used to represent the a single calendar day, it was created
 * because the Java library classes do not satisfy the need for the project.
 * It holds a month, day, and year.  There are accessors and mutator methods
 * for the fields.  It implements the Comparable interface, so the relationship
 * two different objects are known.  An important note to using this class
 * is that the first month, January,  is represented by the number zero.
 * A one is used for February and so on.
 * 
 * Class Invariant: month, day and year can not be negative
 */

package ocrs.server.registeringSystem;

import java.util.GregorianCalendar;

import com.google.appengine.api.datastore.Key;
//import com.sun.istack.internal.NotNull;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;


@PersistenceCapable
public class MonthDayYear implements Comparable<MonthDayYear> 
{
	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Key theKey;
	
	@Persistent
    private int month;                            // January = 0
	
	@Persistent
    private int day;
	
	@Persistent
    private int year;
	
	private static final String[] monthNames = {"January", "February", "March",
                                                "April", "May", "June", "July",
                                                "August", "September", 
                                                "October", "November",
                                                "December"};
    
    
	/** 
     * Name: MonthDayYear <br><br>
     * The default constructor that initializes the private data members.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: The private data members are initialized. 
     * <br>
     * @param None
     */
    public MonthDayYear()
    {
    	month = 0;
    	day = 0;
    	year = 0;
    } // end of default constructor
    
    
    
    /** 
     * Name: MonthDayYear <br><br>
     * The constructor that initializes the private data members with the
     * values passed in.  January is represented by one.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Initializes the private data members with the arguments.
     * @param None
     * @throws NotValidArgumentException is thrown is a negative number is
     *         passed in.
     */
    public MonthDayYear(int month, int day, int year) 
                                               throws NotValidArgumentException
    {
    	if(month < 0 || day < 0 || year < 0)
    		throw new NotValidArgumentException("Can not create the " +
    				                            "MonthDayYear because a " +
    				                            "negative value was passed " +
    				                            "in!\n");
    	
    	String whyWasTheInputNotValid = 
    		                   checkIfTheCalendarDateIsValid(month, day, year);
    	
    	if(whyWasTheInputNotValid.equals("") == false)
    		throw new NotValidArgumentException(whyWasTheInputNotValid);
    	
    	this.month = month;
    	this.day = day;
    	this.year = year;
    } // end of constructor
    
    
    
    /** 
     * Name: MonthDayYear <br><br>
     * The copy constructor that initializes the private data members with the
     * same values with as the argument.  It is a deep copy.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Performs a deep copy of the argument object.
     * @param copyThis - the item that will be copied
     * @throws NullReferenceException is thrown is when a null reference was 
     *         passed in
     */
    public MonthDayYear(MonthDayYear copyThis) throws NullReferenceException
    {
    	if(copyThis == null)
    		throw new NullReferenceException("Can not copy and create the " +
    				                         "MonthDayYear object because " +
    				                         "a null reference was passed " +
    				                         "in!\n");
    	
    	month = copyThis.month;
    	day = copyThis.day;
    	year = copyThis.year;
    } // end of copy constructor
    
    
    
    /** 
     * Name: getTheKey <br><br>
     * An accessor method that retrieves the the key for the object in the
     * Google datastore.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Returns the key. <br>
     * @param None
     */
    public Key getTheKey()
    {
    	return theKey;
    } // end of getTheKey()
    
    
    
    /** 
     * Name: getMonth <br><br>
     * Accessor that retrieves the month field.  Janurary is represented by
     * one.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Retrieves the month field. 
     * <br>
     * @param None
     */
    public int getMonth()
    {
    	return month;
    } // end of getMonth()
    
    
    
    /** 
     * Name: getDay <br><br>
     * Accessor that retrieves the day field.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Retrieves the day field. 
     * <br>
     * @param None
     */
    public int getDay()
    {
    	return day;
    } // end of getDay()
    
    
    
    /** 
     * Name: getYear <br><br>
     * Accessor that retrieves the year field.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Retrieves the year field. 
     * <br>
     * @param None
     */
    public int getYear()
    {
    	return year;
    } // end of getYear()
    
    
    
    /** 
     * Name: setMonth <br><br>
     * Mutator that changes the month field.  January is represented by zero.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Changes the month field. 
     * <br>
     * @param month - the month that will be set to the calling object
     * @throws NotValidInputException is a unchecked exception that is thrown
     *         when a negative number is passed in.
     */
    public void setMonth(int month) throws NotValidArgumentException
    {
    	if(month < 0)
    		throw new NotValidArgumentException("Can not set the month " +
    				                            "because a negative number " +
    				                            "was passed in!\n");
    	
    	else if(month > 11)
    		throw new NotValidArgumentException("Can not set the month " +
                                                "because there are only " +
                                                "12 months!\n" +
                                                "Please remember that the " +
                                                "first month, Janurary, is " +
                                                "represented by zero!\n");
    	
    	this.month = month;
    } // end of setMonth()
    
    
    
    /** 
     * Name: setDay <br><br>
     * Mutator that changes the day field.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Changes the day field. 
     * <br>
     * @param day - the day that will be set to the calling object
     * @throws NotValidInputException is a unchecked exception that is thrown
     *         when a negative number is passed in.
     */
    public void setDay(int day) throws NotValidArgumentException
    {
    	if(day < 0)
    		throw new NotValidArgumentException("Can not set the day " +
    				                            "because a negative " +
    				                            "number was passed in!\n");
    	
    	this.day = day;
    } // end of setDay()
    
    
    
    /** 
     * Name: setYear <br><br>
     * Mutator to change the year field.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Changes year field with the value passed in. 
     * <br>
     * @param year - the year that will be set to the calling object
     * @throws NotValidInputException is a unchecked exception that is thrown
     *         when a negative number is passed in.
     */
    public void setYear(int year) throws NotValidArgumentException
    {
    	if(year < 0)
    		throw new NotValidArgumentException("Can not set the year " +
    				                            "because a negative number " +
    				                            "was passed in!\n");
    	
    	this.year = year;
    } // end of setYear()
    
    
    
    /** 
     * Name: setAllThreeFields <br><br>
     * Sets all three fields at the same time.  January is represented by zero.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Changes all three fields at the same time. 
     * <br>
     * @param month - the month that will be set to the calling object
     * @param day - the day that will be set to the calling object
     * @param year - the year that will be set to the calling object
     * @throws NotValidInputException is a unchecked exception that is thrown
     *         when a negative number is passed in.
     */
    public void setAllThreeFields(int month, int day, int year)
                                               throws NotValidArgumentException
    {
    	if(month < 0 || day < 0 || year < 0)
    		throw new NotValidArgumentException("Can not set all three " +
    				                            "fields because a negative " +
    				                            "number passed in!\n");
    		
    	this.month = month;
    	this.day = day;
    	this.year = year;
    } // end of setAllThreeFields()
    
    
    
    /** 
     * Name: isThisALeapYear <br><br>
     * Determines if the year is a leap year.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Returns a boolean stating if the year is a leap year. 
     * <br>
     * @param year - the year that will be checked
     * @throws NotValidInputException is a unchecked exception that is thrown
     *         when a negative number is passed in.
     */
    public static boolean isThisALeapYear(int year) 
                                               throws NotValidArgumentException
	{
    	if(year < 0)
    		throw new NotValidArgumentException("Can not determine if the " +
    				                            "year is a leap year " +
    				                            "because the passed in " +
    				                            "number was negative!\n");
    	
		boolean isALeapYear = false;
		boolean isDivisibleBy4 = false;
		boolean isDivisibleBy100 = false;
		boolean isDivisibleBy400 = false;
		
		if(year >= 0)
		{	
		    isDivisibleBy4 = (year % 4) == 0;
		    isDivisibleBy100 = (year % 100) == 0;
		    isDivisibleBy400 = (year % 400) == 0;
		    
		    isALeapYear = (isDivisibleBy400) || 
		                                 (!isDivisibleBy100 && isDivisibleBy4);
		} // end of if statement
		
		return isALeapYear;
	} // end of isLeapYear()
	
    
    
    /** 
     * Name: checkIfTheCalendarDateIsValid <br><br>
     * Determines if the passed numbers forms a valid date. 
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Returns a String that states the possible errors with the
     *                arguments passed in. <br>
     * @param month - the number of months
     * @param numberOfDays - the number of days in the month passed in
     * @param year - the year number
     * @throws NotValidArgumentException is thrown if any of the number passed
     *         in is a negative number.
     */
	public static String checkIfTheCalendarDateIsValid(int month, 
			                                           int numberOfDays,
			                                           int year)
	                                           throws NotValidArgumentException
	{
		String returnedString = "";
		String theMonthName = "";
		boolean isNotAValidMonth = false;
		boolean isTheNumberOfDaysValid = false;
		
		if(month >= 0 && numberOfDays > 0 && year >= 0)
		{
			boolean isALeapYear = isThisALeapYear(year);
			
			switch(month)
			{
			    case GregorianCalendar.JANUARY:
			    	theMonthName += monthNames[0];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 31);
			    	break;
			    	
			    case GregorianCalendar.FEBRUARY:
			    	theMonthName += monthNames[1];
			    	isTheNumberOfDaysValid = (numberOfDays >= 1 && 
			    			                  numberOfDays <= 28 && 
			    			                  !isALeapYear) ||
			    			                 (numberOfDays >= 1 &&
			    			                  numberOfDays <= 29 &&
			    			                  isALeapYear);
			    	System.out.println("No. of days valid: " + isTheNumberOfDaysValid);
			    	break;
			    	
			    case GregorianCalendar.MARCH:
			    	theMonthName += monthNames[2];
			    	isTheNumberOfDaysValid = numberOfDays >= 1 &&
			    	                         numberOfDays <= 31;
			    	break;
			    	
			    case GregorianCalendar.APRIL:
			    	theMonthName += monthNames[3];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 30);
			    	break;
			    	
			    case GregorianCalendar.MAY:
			    	theMonthName += monthNames[4];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 31);
			    	break;
			    	
			    case GregorianCalendar.JUNE:
			    	theMonthName += monthNames[5];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 30);
			    	break;
			    	
			    case GregorianCalendar.JULY:
			    	theMonthName += monthNames[6];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 31);
			    	break;
			    	
			    case GregorianCalendar.AUGUST:
			    	theMonthName += monthNames[7];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 31);
			    	break;
			    	
			    case GregorianCalendar.SEPTEMBER:
			    	theMonthName += monthNames[8];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 30);
			    	break;
			    	
			    case GregorianCalendar.OCTOBER:
			    	theMonthName += monthNames[9];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 31);
			    	break;
			    	
			    case GregorianCalendar.NOVEMBER:
			    	theMonthName += monthNames[10];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 30);
			    	break;
			    	
			    case GregorianCalendar.DECEMBER:
			    	theMonthName += monthNames[11];
			    	isTheNumberOfDaysValid = 
			    		             (numberOfDays >= 1 && numberOfDays <= 31);
			    	break;
			    	
			    default:
			    	theMonthName += "NOT VALID";
			    	isTheNumberOfDaysValid = false;
			    	isNotAValidMonth = true;
			    	break;
			} // end of the switch statement
			
			
			if(!isNotAValidMonth && !isTheNumberOfDaysValid)
				returnedString = theMonthName + " did not contain the " +
				                 "correct number of days!\n"; 
			
			else if(isNotAValidMonth)
				returnedString = "The month was " + theMonthName;
		} // end of if statement
		
		else
			throw new NotValidArgumentException("It is not a valid date " +
					                            "a negative number was " +
					                            "passed in!\n");
		
		return returnedString;
	} // end of checkIfTheCalendarDateIsValid()
	
	
	
	/** 
     * Name: toString <br><br>
     * Displays the objects state.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Returns a String representing the object's state. <br>
     * @param None
     */
	public String toString()
	{
		return monthNames[month] + " " + day + ", " + year + "\n";
	} // end of toString()
	
	
	
	/** 
     * Name: equals <br><br>
     * Compares two objects are equal. 
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Returns a boolean stating if the objects are equal. <br>
     * @param otherMonthDayYear - the other object will be compared
     */
	public boolean equals(MonthDayYear otherMonthDayYear)
	{
		boolean areTheObjectsEqual;
		
		if(otherMonthDayYear == null)
			areTheObjectsEqual = false;
		
		else
			areTheObjectsEqual = month == otherMonthDayYear.month &&
			                     day == otherMonthDayYear.day &&
			                     year == otherMonthDayYear.year;
		
		return areTheObjectsEqual;
	} // end of equals()
	
	
	
	/** 
     * Name: compareTo <br><br>
     * Compares two objects and returns a number stating the ordering between
     * two objects.  A positive number states that the calling object would
     * come after the other object.  In contrast a negative number would
     * represent a smaller number.  Zero means that the two object are
     * equivalent.  The objects calling this needs to check for 
     * NullPointerExceptions at runtime, so have this inside a try block.
     * <br><br>
     * Precondition: None <br>
     * Postcondition: Returns a number stating the ordering relationship 
     *                between the calling object and the argument. <br>
     * @param otherMonthDayYear - the other object will be compared
     * @throws NullPointerException is a unchecked exception that is thrown
     *         when a null argument is passed in
     */
	public int compareTo(MonthDayYear otherMonthDayYear)
	{
		if(otherMonthDayYear == null)
			throw new NullPointerException("Can not compare the objects " +
					                       "argument is a null reference!\n");
		
		int compareToValue = 0;
		
		// finding the relationship between the calling
		// object and reference object
		// first checking the year
		if(year > otherMonthDayYear.year)
			compareToValue = 1;
		
		// next checking the months
		else if(month > otherMonthDayYear.month)
            compareToValue = 1;
		
		// next checking the days
		else if(day > otherMonthDayYear.day)
			compareToValue = 1;
		
		// checking if the days are equal
		// if they are then that means the
		// objects are equivalent
		else if(day == otherMonthDayYear.day)
			compareToValue = 0;
		
		// the other object must be larger than this object
		else
			compareToValue = -1;
		
		return compareToValue;
	} // end of compareTo()
} // end of MonthDayYear class